/*
 * Copyright (c) 2009, Russell Weir
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "hxvm.h"
#include "hxvm_python.h"
#include "hashtool.h"
#include <datetime.h>

static PyObject* python_main;
static PyObject* python_main_dict;

typedef struct PyIterData {
	int ok;
	PyObject *rv;
	value object;
} IterData;

DEFINE_KIND( k_python_vm );
DEFINE_KIND( k_pyobject_pointer );


// Forward declarations
static PyObject* vmpython_create_native(value v);
// common methods
#include "hxvm.cpp"

static VM_Descriptor* vmpython_get_descriptor( value vm )
{
	if ( !val_is_kind( vm, k_python_vm ) )
		return NULL;
	VM_Descriptor *vd = VM( vm );
	if(!vd->python_state) {
		setError(vd, "Bad python state");
		return NULL;
	}
	return vd;
}

void vmpython_destroy( value vm )
{
// 	printf("hxvm_destroy\n");
	if ( !val_is_kind( vm, k_python_vm ) )
		return;

	val_gc( vm, NULL );
	VM_Descriptor *vd = VM( vm );
	if( !vd ) return;
	if(vd->python_state) {
		Py_DECREF(vd->python_state);
		vd->python_state = NULL;
	}
	clearError(vd);
	free( vd );
}

void vmpython_destroy_pyobject( value po )
{
// 	printf("hxvm_destroy\n");
	if ( !val_is_kind( po, k_pyobject_pointer ) )
		return;

	val_gc( po, NULL );
	PyObject *obj = PYOBJPOINTER( po );
	if( !obj ) return;
	Py_DECREF(obj);
}

/**
* Convenience method for setting the last_error and returning
* a neko String value for it.
*
* @return neko_error always
**/
static value vmpython_error(VM_Descriptor *vd, const char *defaultMsg=NULL)
{
	if( vd->type != VM_PYTHON)
		neko_error();
	if(PyErr_Occurred() == NULL) {
		if(defaultMsg)
			setError(vd, defaultMsg);
		else
			setError(vd, "Unknown error not related to python.");
		neko_error();
	}
	if(PyErr_ExceptionMatches(PyExc_Exception))
		setError(vd, "Python: exception.");
	else if(PyErr_ExceptionMatches(PyExc_StandardError))
		setError(vd, "Python: standard error.");
	else if(PyErr_ExceptionMatches(PyExc_ArithmeticError))
		setError(vd, "Python: arithmetic error.");
	else if(PyErr_ExceptionMatches(PyExc_LookupError))
		setError(vd, "Python: lookup error.");
	else if(PyErr_ExceptionMatches(PyExc_AssertionError))
		setError(vd, "Python: assertion error.");
	else if(PyErr_ExceptionMatches(PyExc_AttributeError))
		setError(vd, "Python: attribute error.");
	else if(PyErr_ExceptionMatches(PyExc_EOFError))
		setError(vd, "Python: EOF error.");
	else if(PyErr_ExceptionMatches(PyExc_EnvironmentError))
		setError(vd, "Python: enviroment error.");
	else if(PyErr_ExceptionMatches(PyExc_FloatingPointError))
		setError(vd, "Python: floating point error.");
	else if(PyErr_ExceptionMatches(PyExc_IOError))
		setError(vd, "Python: IO error.");
	else if(PyErr_ExceptionMatches(PyExc_ImportError))
		setError(vd, "Python: import error.");
	else if(PyErr_ExceptionMatches(PyExc_IndexError))
		setError(vd, "Python: index error.");
	else if(PyErr_ExceptionMatches(PyExc_KeyError))
		setError(vd, "Python: key error.");
	else if(PyErr_ExceptionMatches(PyExc_KeyboardInterrupt))
		setError(vd, "Python: keyboard interrupt.");
	else if(PyErr_ExceptionMatches(PyExc_MemoryError))
		setError(vd, "Python: memory error.");
	else if(PyErr_ExceptionMatches(PyExc_NameError))
		setError(vd, "Python: name error.");
	else if(PyErr_ExceptionMatches(PyExc_NotImplementedError))
		setError(vd, "Python: not implemented error.");
	else if(PyErr_ExceptionMatches(PyExc_OSError))
		setError(vd, "Python: os error.");
	else if(PyErr_ExceptionMatches(PyExc_OverflowError))
		setError(vd, "Python: overflow error.");
	else if(PyErr_ExceptionMatches(PyExc_ReferenceError))
		setError(vd, "Python: reference error.");
	else if(PyErr_ExceptionMatches(PyExc_RuntimeError))
		setError(vd, "Python: runtime error.");
	else if(PyErr_ExceptionMatches(PyExc_SyntaxError))
		setError(vd, "Python: syntax error.");
	else if(PyErr_ExceptionMatches(PyExc_SystemError))
		setError(vd, "Python: system error.");
	else if(PyErr_ExceptionMatches(PyExc_SystemExit))
		setError(vd, "Python: system exit.");
	else if(PyErr_ExceptionMatches(PyExc_TypeError))
		setError(vd, "Python: type error.");
	else if(PyErr_ExceptionMatches(PyExc_ValueError))
		setError(vd, "Python: value error.");
#if BUILD_WINDOWS
	else if(PyErr_ExceptionMatches(PyExc_WindowsError))
		setError(vd, "Python: windows error.");
#endif
	else if(PyErr_ExceptionMatches(PyExc_ZeroDivisionError))
		setError(vd, "Python: division by zero error.");
	else
		setError(vd, "Unknown Python error.");
	PyErr_Clear();
	neko_error();
}

/**
* Returns a value for the provided PyObject
*
* @param obj Object to convert
* @param res Result of conversion, 0 on failure
*/
static value vmpython_create_value(PyObject *obj, int *res)
{
	int a, i, len;
	value rv = val_null, sv = val_null;
	PyDateTime_IMPORT;

	if(res) *res = 1;
	if(obj == Py_None) {
		return val_null;
	}
	else if(PyBool_Check(obj)) {
		if (PyObject_Cmp(obj, Py_True, &a) == -1) {
			// -1 is failure
			fprintf(stderr,"Python bool object comparison failure\n");
			if(res) *res = 0;
			return val_null;
		}
		if(a == -1)
			return val_false;
		return val_true;
	}
	else if(PyInt_Check(obj)) {
		return alloc_best_int(PyInt_AsLong(obj));
	}
	else if(PyLong_Check(obj)) {
		return alloc_float(PyLong_AsDouble(obj));
	}
	else if(PyFloat_Check(obj)) {
		return alloc_float(PyFloat_AsDouble(obj));
	}
	else if(PyString_Check(obj)) {
		return alloc_string(PyString_AsString(obj));
	}
	else if(PyList_Check(obj)) {
		len = PyList_Size(obj);
		if(len >= 0) {
			rv = alloc_array( len );
			for (i=0; i<len; i++) {
				val_array_ptr(rv)[i] = vmpython_create_value(PyList_GetItem(obj, i), &a);
				if(!a) return val_null;
			}
		} else {
			rv = alloc_array( 0 );
		}
		return rv;
	}
	else if(PyTuple_Check(obj)) {
		len = PyTuple_Size(obj);
		if(len >= 0) {
			rv = alloc_array( len );
			for (i=0; i<len; i++) {
				val_array_ptr(rv)[i] = vmpython_create_value(PyTuple_GetItem(obj, i), &a);
				if(!a) return val_null;
			}
		} else {
			rv = alloc_array( 0 );
		}
		return rv;
	}
	else if(PyAnySet_Check(obj)) {
		len = PySet_Size(obj);
		if(len >= 0) {
			rv = alloc_array( len );
			for (i=0; i<len; i++) {
				val_array_ptr(rv)[i] = vmpython_create_value(PySet_Pop(obj), &a);
				if(!a) return val_null;
			}
		} else {
			rv = alloc_array( 0 );
		}
		return rv;
	}
	else if(PyDate_Check(obj) || PyDateTime_Check(obj)) {
		rv = alloc_object(0);

		alloc_field( rv, val_id( "year" ), alloc_int(PyDateTime_GET_YEAR(obj)) );
		alloc_field( rv, val_id( "month" ), alloc_int(PyDateTime_GET_MONTH(obj)) );
		alloc_field( rv, val_id( "day" ), alloc_int(PyDateTime_GET_DAY(obj)) );

		if( PyDateTime_Check(obj) ) {
			alloc_field( rv, val_id( "hour" ), alloc_int(PyDateTime_DATE_GET_HOUR(obj)) );
			alloc_field( rv, val_id( "min" ), alloc_int(PyDateTime_DATE_GET_MINUTE(obj)) );
			alloc_field( rv, val_id( "sec" ), alloc_int(PyDateTime_DATE_GET_SECOND(obj)) );
		}

		return alloc_object(rv);
	}
	else if(PyDict_Check(obj)) {
		PyObject *key, *val;
		value n_key, n_value;
		Py_ssize_t pos = 0;

		vhash *h = hash_new();
		rv = hash_to_value(h);
		while (PyDict_Next(obj, &pos, &key, &val)) {
			n_key = vmpython_create_value(key, &a);
			if(!a) {
				if(res) *res = 0;
				fprintf(stderr, "vmpython_create_value: Python dictionary key error\n");
				return val_null;
			}
			n_value = vmpython_create_value(val, &a);
			if(!a) {
				if(res) *res = 0;
				fprintf(stderr, "vmpython_create_value: Python dictionary key error\n");
				return val_null;
			}
			hash_set(h, n_key, n_value);
		}
		return rv;
	}

	fprintf(stderr, "vmpython_create_value: Python object type not supported in return value\n");
	if(res) *res = 0;
	return val_null;
}

static void vmpython_set_field_rec( value data, field fId, void *stat )
{
	PyIterData *idata = (IterData *) stat;
	PyObject *pyKey, *pyVal;

	if(!idata || !idata->ok || !idata->rv)
		return;

	value key = val_field_name( fId );
	if(val_is_string(key)) {
		pyKey = PyString_FromString(val_string(key));
		if(!pyKey) {
			fprintf(stderr, "vmpython_set_field_rec: Key error. Please report.\n");
			idata->ok = 0;
			return;
		}
		pyVal = vmpython_create_native(data);
		if(!pyVal) {
			fprintf(stderr, "vmpython_set_field_rec: Value error. Please report.\n");
			idata->ok = 0;
			return;
		}
		// steals the reference to pyVal (ie. No Py_DECREF required)
		PyDict_SetItem(idata->rv, pyKey, pyVal);
	}
	else {
		fprintf(stderr, "vmpython_set_field_rec: Internal error #%d. Please report.\n", val_type(key));
		idata->ok = 0;
	}
}

/**
* Creates a PyObject from a neko value. Returns null if the conversion
* can not be done. The returned PyObject must have Py_DECREF called on it
* after it is used.
*
* @param v Neko value
* @return new PyObject, or null on failure
**/
static PyObject* vmpython_create_native(value v)
{
	PyObject *rv = NULL, *p;
	int len, i;
	value v2;
	IterData idata;

	switch( val_type(v) ) {
	case VAL_NULL:
#if BUILD_WINDOWS
		// http://effbot.org/pyfaq/how-can-i-embed-python-into-a-windows-application.htm
		return Py_BuildValue("");
#else
		Py_RETURN_NONE;
#endif
		break;
	case VAL_INT:
		rv = PyInt_FromLong(val_int(v));
		break;
	case VAL_FLOAT:
		rv = PyFloat_FromDouble(val_float(v));
		break;
	case VAL_BOOL:
		rv = PyBool_FromLong(val_bool(v));
		break;
	case VAL_ARRAY:
		len = val_array_size(v);
		rv = PyList_New(len);
		for( i=0; i<len; i++) {
			p = vmpython_create_native(val_array_ptr(v)[i]);
			if(!p) {
				fprintf(stderr, "Unable to populate python list. Null detected\n");
				Py_DECREF(rv);
				return NULL;
			}
			// steals reference to p (ie. No Py_DECREF required)
			PyList_SET_ITEM(rv, i, p);
		}
		break;
	case VAL_FUNCTION:
		fprintf(stderr, "VAL_FUNCTION not handled\n");
		break;
	case VAL_STRING:
		rv = PyString_FromString(val_string(v));
		break;
	case VAL_OBJECT:
		fprintf(stderr, "VAL_OBJECT\n");
		rv = PyDict_New();
		idata.ok = 1;
		idata.rv = rv;
		idata.object = v;
		val_iter_fields(v, vmpython_set_field_rec, &idata);
		if(!idata.ok) {
			Py_DECREF(rv);
			return NULL;
		}
		break;
	case VAL_ABSTRACT:
		if( val_is_kind(v, k_int32) ) {
			rv = PyInt_FromLong(val_int32(v));
		}
		else if( val_is_kind(v,k_hash) ) {
			vhash *h = val_hdata(v);
			rv = PyDict_New();
			for(i=0; i<h->ncells; i++) {
				hcell *c = h->cells[i];
				while( c != NULL ) {
					PyObject *key = vmpython_create_native(c->key);
					PyObject *val = vmpython_create_native(c->val);
					if(!key || !val) {
						Py_DECREF(rv);
						return NULL;
					}
					PyDict_SetItem(rv, key, val);
					c = c->next;
				}
			}
		}
		if(val_is_kind(v, k_pyobject_pointer)) {
			rv = PYOBJPOINTER( v );
		}
		else if(val_is_kind(v, k_pointer)) {
			VM_Pointer *p = POINTER(v);
			rv = PyBuffer_FromMemory(p->data, 0);
		}
		break;
	}
	return rv;
}



////////////////////////////////////////////////////////////
////                  PYTHON API                        ////
////////////////////////////////////////////////////////////



/**
* Startup
**/
value vmpython_initialize()
{
	try {
		Py_Initialize();

		python_main = PyImport_AddModule("__main__");
		python_main_dict = PyModule_GetDict(python_main);
		if(!python_main || !python_main_dict)
			goto error;


		Py_INCREF(python_main);
		Py_INCREF(python_main_dict);
		return val_true;
	} catch(...) {}
error:
	fprintf(stderr,"Unable to initialize python\n");
	python_main = NULL;
	python_main_dict = NULL;
	return val_false;
}
DEFINE_PRIM( vmpython_initialize, 0 );

/**
* Returns the last error as a string. If there is no
* error, null is returned.
*
* @return String error message or null
**/
value vmpython_get_error(value vm)
{
	VM_Descriptor *vd = vmpython_get_descriptor( vm );
	if(!vd)
		return alloc_string("VM not allocated or pointer invalid.");

	if(vd->last_error == NULL)
		return alloc_string("");
	value rv = alloc_string((const char *) vd->last_error);
	return rv;
}
DEFINE_PRIM( vmpython_get_error, 1 );

/**
* Create a new vm
*
* @param type "lua" or ...
**/
value vmpython_create()
{
	value rv;
	if(!python_main || !python_main_dict) {
		fprintf(stderr, "Python not initialized\n");
		neko_error();
	}

	VM_Descriptor *vd = (VM_Descriptor *)malloc(sizeof(VM_Descriptor));
	if(!vd) return val_null;
	vd->type == VM_UNKNOWN;
	vd->multiReturnMode = 0;
	vd->last_error = NULL;

	vd->python_state = PyDict_Copy(python_main_dict);
	if( vd->python_state == NULL )
		goto fatal;
	vd->type = VM_PYTHON;
	Py_INCREF(vd->python_state);

	rv = alloc_abstract( k_python_vm, vd );
	val_gc( rv, vmpython_destroy );

	return rv;
fatal:
	fprintf(stderr, "vm create failed\n");
	free(vd);
	return val_null;
}
DEFINE_PRIM( vmpython_create, 0 );

/**
* Returns the runtime version of the VM as a string
*
* @return Full vm version string
**/
value vmpython_getversion(value vm)
{
	val_check_kind( vm, k_python_vm );

	VM_Descriptor *vd = vmpython_get_descriptor(vm);
	if( !vd ) neko_error();

	return alloc_string(Py_GetVersion());
}
DEFINE_PRIM( vmpython_getversion, 1 );

/**
* Open a standard library
*
* @param vm
* @param name Library name to open
* @return Bool true on success
**/
value vmpython_openstdlib(value vm, value name)
{
	VM_Descriptor *vd = vmpython_get_descriptor(vm);
	if(!vd)
		neko_error();
	val_check(name, string);
	return val_false;
}
DEFINE_PRIM( vmpython_openstdlib, 2 );

/**
* Loads a script from a file path
*
* @param vm vm pointer
* @param filename String file path
* @return Bool true on success
**
value vmpython_exec_file(value vm, value filename)
{
	PyObject* callRes = NULL;
	int res = 0;

	VM_Descriptor *vd = vmpython_get_descriptor(vm);
	if(!vd) neko_error();
#if BUILD_WINDOWS
	// http://effbot.org/pyfaq/pyrun-simplefile-crashes-on-windows-but-not-on-unix-why.htm
	// This should never be called in windows
	setError(vd, "This function should not be used in Windows. Load bytes in haxe and call exec() instead. http://effbot.org/pyfaq/pyrun-simplefile-crashes-on-windows-but-not-on-unix-why.htm");
	neko_error();
#endif

	if( val_is_null(filename) ) {
		setError(vd, "Filename is null.");
		neko_error();
	}
	val_check( filename, string );
	if(val_strlen(filename) == 0) {
		setError(vd, "Filename is empty.");
		neko_error();
	}

	clearError(vd);

	const char* file = val_string( filename );

	try {
		FILE *fp = fopen(file, "r");
		if(!fp) {
			setError(vd, "Unable to open input file");
			neko_error();
		}
		// parse script
		callRes = PyRun_FileExFlags(fp, file, Py_file_input, vd->python_state, vd->python_state, 1, NULL); // fp will be closed.
		if(!callRes) {
			setError(vd, "Error loading script");
			neko_error();
		}
	}
	catch (...) {
		if(callRes) Py_DECREF(callRes);
		neko_error();
	}
	value rv = vmpython_create_value(callRes, &res);
	Py_DECREF(callRes);
	if(res) return rv;

	// error handling
	clearError( vd );
	vd->last_error = (char *)malloc(200 + val_strlen(filename));
	sprintf(vd->last_error, "python call : %s : exec failed.", val_string(filename));
	fprintf(stderr, "%s\n", vd->last_error);
	neko_error();
}
DEFINE_PRIM( vmpython_exec_file, 2 );
*/

/**
* Loads a script from a byte buffer
*
* @param vm vm pointer
* @param code string containing code to run
* @return Bool true on success
**/
value vmpython_exec(value vm, value code) {
	int res = 0;

	VM_Descriptor *vd = vmpython_get_descriptor(vm);
	if(!vd) neko_error();
	val_check( code, string );

	clearError(vd);

	PyObject *callRes = PyRun_String(val_string(code), Py_file_input, vd->python_state, vd->python_state);

	if(!callRes)
		return vmpython_error( vd, "Code compile failure." );

	value rv = vmpython_create_value(callRes, &res);
	Py_DECREF(callRes);
	return rv;
}
DEFINE_PRIM( vmpython_exec, 2 );

value vmpython_call_function(value vm, value functionName, value inArgs)
{
	PyObject *function;
	PyObject *tuple;
	PyObject *param;
	PyObject *callRes;
	value p, rv = val_null;
	int i, n;

	VM_Descriptor *vd = vmpython_get_descriptor(vm);
	if(!vd) neko_error();
	clearError(vd);
	val_check( functionName, string );
	val_check( inArgs, array );


	function = PyDict_GetItemString(vd->python_state, val_string(functionName));
	if(!function) {
		vd->last_error = (char *)malloc(200 + val_strlen(functionName));
		sprintf(vd->last_error, "python call : %s : function not found", val_string(functionName));
		return val_false;
	}

	n = val_array_size(inArgs);
	value* args = val_array_ptr(inArgs);
	tuple = PyTuple_New(n);
	if(!tuple) {
		vd->last_error = (char *)malloc(200 + val_strlen(functionName));
		sprintf(vd->last_error, "python call : %s : memory error", val_string(functionName));
		return val_false;
	}
	for(i=0; i<n; i++) {
		param = vmpython_create_native(args[i]);
		if(!param)
			goto argError;
		if(PyTuple_SetItem(tuple, i, param) != 0)
		{
			Py_DECREF(tuple);
			vd->last_error = (char *)malloc(200 + val_strlen(functionName));
			sprintf(vd->last_error, "python call : %s : argument creation error", val_string(functionName));
			return val_false;
		}
	}

	try {
		callRes = PyObject_CallObject(function, tuple);
		Py_DECREF(tuple);
		if(!callRes) {
			fprintf(stderr, "Python callRes error\n");
			vd->last_error = (char *)malloc(200 + val_strlen(functionName));
			sprintf(vd->last_error, "python call : %s : call failed", val_string(functionName));
			return val_false;
		}
	} catch(...) {
		fprintf(stderr, "Python call error\n");
		Py_DECREF(tuple);
		if(callRes)
			Py_DECREF(callRes);
		return vmpython_error( vd );
	}
	rv = vmpython_create_value(callRes, &n);
	Py_DECREF(callRes);
	if(n) return rv;

argError:
	if(tuple)
		Py_DECREF(tuple);
	clearError( vd );
	vd->last_error = (char *)malloc(200 + val_strlen(functionName));
	sprintf(vd->last_error, "python call : %s : invalid type for argument %d", val_string(functionName), i+1);
	fprintf(stderr, "%s\n", vd->last_error);
	return val_false;
}
DEFINE_PRIM( vmpython_call_function, 3 );

value vmpython_makenativedate(value vm, value oDate)
{
	PyDateTime_IMPORT;
	value year, month, day, hour, min, sec;

	VM_Descriptor *vd = vmpython_get_descriptor(vm);
	if(!vd) neko_error();

	year = val_field(oDate,val_id("year"));
	month = val_field(oDate,val_id("month"));
	day = val_field(oDate,val_id("day"));
	hour = val_field(oDate,val_id("hour"));
	min = val_field(oDate,val_id("min"));
	sec = val_field(oDate,val_id("sec"));

	val_check(year,int);
	val_check(month,int);
	val_check(day,int);
	val_check(hour,int);
	val_check(min,int);
	val_check(sec,int);

	PyObject *obj = PyDateTime_FromDateAndTime(val_int(year), val_int(month), val_int(day), val_int(hour), val_int(min), val_int(sec), 0);
	value rv = alloc_abstract( k_pyobject_pointer, obj );
	val_gc( rv, vmpython_destroy_pyobject );
	return rv;
}
DEFINE_PRIM( vmpython_makenativedate, 2 );
